local filename = "agent.lua"

local skynet = require "skynet"
local cluster = require "skynet.cluster"
require "skynet.manager"
local queue = require "skynet.queue"

local netpack = require "skynet.websocketnetpack"

local json = require "cjson"
json.encode_sparse_array(true,1,1)

local tonumber = tonumber
local filelog = require "filelog"
local configdao = require "configdao"
local timetool = require "timetool"
local tabletool = require "tabletool"
local redisdao = require "redisdao"
local md5 = require "md5"

local dbstruct = require "dbstruct"
local base = require "base"

require "pubenum"

--[[
EGateAgentState = {
	GATE_AGENTSTATE_UNKNOW = 0,	--初始状态
	GATE_AGENTSTATE_LOGINING = 1,	--正在登陆
	GATE_AGENTSTATE_LOGINED = 2, 	--登陆成功
	GATE_AGENTSTATE_LOGOUTING = 3, --正在登出
	GATE_AGENTSTATE_LOGOUTED = 4,	--退出成功
}
]]

local Agent = {
	gate_service = nil,
	watch_dog = nil,
	client_fd = nil,
	socket = nil,
	eventpool = {},
	link_ip="", -- 链接来的ip地址，可能是被cdn处理之前的，
	ip="", -- 客户端repotip，是完全真实的ip地址
    gps_addr = "",
	port="",
	uid = 0,
	rid = 0,
	state = 0,  --登陆状态 0 已经退出 1 正在登陆 2 登陆成功 3 正在退出 
	last_heart_time = 0, -- 最后心跳，活动时间
	enter_time	= 0,	-- 进入游戏时间
	isoffline = false,	-- 客户端是否断线了（socket断了，agent还在）

	--玩家基本信息,一个属性对应一张表
	info = nil,
	money = nil,

	--渠道版本信息
	platform = 0, --client 平台id(属于哪家公司发行)
	channel = 0,  --client 渠道id(发行公司的发行渠道)
	version = "", --client 版本号
	authtype = 0, --client 账号类型

	-- 用户在游戏节点，进入桌子后更改状态，这样断线重连的时候，可以拉入桌子
	gamenode = "", -- 这里可能是 gamenodea, gamenodeb, 要根据这个去转发牌桌消息
	table_index = 0,
	-- table_state = 0 -- 桌子状态，准备阶段，开始run阶段
	-- seat_index = 0,

	-- 此处以后再优化，可以使用共享内存表 datasheet 来优化
	item2table = {}, -- 数据库字段到每个表的对应关系
	qiniu_access_key='',  -- 七牛key
	qiniu_secret_key='',  -- 七牛key

	hrlindex = 0,	-- 动态跑马灯发送到哪里了

	-- builder = builder,
	-- datasheet = datasheet,
	gamenode_config_ab = {} ,

    match_gamenode = "",
    match_tabletype = nil,
}

local agent_heart_timeout

local cs = queue()
--[[
	在线的游戏node 的具体的A，B配置，默认agent 启动的时候更新
	有一个接口，可以改变，由外部调用
	{mjzznode= "a"}
]]

-- 因为函数名含了点(.),所以不能再使用 function AgentCMD:xxx.yyy 的方式了。
-- 这种含了命名空间的函数，默认都会call另外一个节点的函数了
-- 特殊时候，可以在这里定义一个函数，这样就可以拦截去cluster的操作为本地调用
-- AgentCMD['gone.intogame'] = function(...)
-- 	-- return { result = "Welcome to gone." }
-- 	-- cluster.call 第一个参数：是cluter节点，第二个参数，是里面的serice，第三个参数是方法。）
-- 	return cluster.call('gone', '.router', 'intogame', ...)
-- end
function Agent:process(session, source, calltype, msgname, ...)
	local f 
	local status, result
	local dotbegin, dotend = 0, 0
	local nodetype, func = '', ''
	if msgname == nil then
		filelog.sys_error(filename.." line74 invalid msgname")
		return
	end
	-- 心跳不打印出来，否则太多了。 需要调试心跳的时候再说
	if msgname ~= "heart" then
		filelog.print(filename.." line85 " .. skynet.self().."__"..calltype.."_"..msgname.."_request --> ", ...)
	end

    --检测是否触发任务
    --skynet.send(".tasksystem", "lua", "check_is_trigger_task", self.rid, msgname, ...)

	f = self.eventpool[msgname]	
	if calltype == "client" then
		-- 是从socket端传来的client消息，这些消息都需要直接返回给客户端
		if f then
			-- Agent 就有处理函数，本节点处理了
			status, result = skynet.pcall(f.process, self.rid, ... )
		else
			-- 看那么是否有点分，如果有点分，说明是调用某个节点的某函数
			-- 如果没有点分，这全部路由到 hall 节点去处理
			-- 点前面是节点的名字，后面是接口的名字 
			dotbegin, dotend = msgname:find('.', 1, true)
			if dotbegin and dotbegin > 0 then
				nodetype, func = msgname:sub(1, dotbegin-1), msgname:sub(dotend+1)
			else
				nodetype = 'hall'
				func = msgname
			end
			-- 这块传给游戏节点的 roledata， 还是可以尽可能优化的
			-- 如果不是每次组装，则修改其中的数据，要修改两次
			local roledata = {info=self.info, money=self.money or {diamond = 0}, ip_addr=self.ip, table_persist=10, gps_addr=self.gps_addr}
			-- 这里因为AB服，要做判断，如果用户已经在游戏中了，就要用原来的 self.gamenode 转发消息
			-- 如果在 ab服中有配置，则需要用ab服
			local realnode = self.gamenode
			local is_goodnode = true
			if  realnode == "" then 
				if self.gamenode_config_ab[nodetype] then
					realnode = nodetype  .. "nod" .. self.gamenode_config_ab[nodetype]
				else
					realnode = nodetype  .. "node"
				end
			else -- 要比较发的协议类型 nodetype ,是否和用户已经在的节点是同一个类型
				if string.sub(self.gamenode, 1, -5) ~= nodetype then
					is_goodnode = false
				end
			end

			if is_goodnode then 
				status, result = skynet.pcall(cluster.call, realnode, '.router', func, self.rid, roledata, ... )
			else
                filelog.sys_error("has be in " ,self.gamenode, self.rid, nodetype, func, msgname, ...)
				status, result = true, {retcode=-101, retstr="游戏异常，请退出游戏重新登录!", msgbody={}}
			end
		end
		-- 这里对返回做统一的处理，看什么情况返回给客户端
		-- 如果返回的result 是 nil， false，则不打包返回, 在写里面的函数的时候注意这一点
		if msgname ~= "heart" then
			filelog.print(filename.." line112 status=", status)
			filelog.print(filename.." line113 result=", result)
		end
		if not status then
			self:send_resmsgto_client(self.client_fd, msgname .. "R", {}, -1, nodetype .. " system error")
		elseif result then
			-- 这里有几种可能
			-- 一、只有 msgbody 的内容， 这样就直接发送了
			-- 二、是一个 table 要根据里面的key[retcode], 来判断，
			-- 如果retcode == 0 ，则retstr  或者 msgbody 至少有一个
			-- 如果retcode ~= 0, 则有retstr
			local retcode, retstr, msgbody = 0, "", {}
			if result.retcode  then
				retcode = tonumber(result.retcode)
				retstr = result.retstr or ""
				msgbody = result.msgbody or {}
			else
				msgbody = result
			end
			self:send_resmsgto_client(self.client_fd, msgname .. "R", msgbody, retcode, retstr)
		end
	else
		-- 是本几点间的service之间调用的 lua 消息
		status, result = skynet.pcall(f.process, session, source, ... )
		if status then
			skynet.retpack(result)
		end
	end
	if not status then
		filelog.sys_error(filename.." line184 skynet.pcall failed", self.rid, msgname, result, ... )
	end
end

-- 需要处理了两个类型的消息，一个是cliet来的，一个是内部的lua
function Agent:start()
	skynet.register_protocol {
		name = "client",
		id = skynet.PTYPE_CLIENT,
		unpack = function (msg, sz)
			return Agent.decode.c2sunpack(Agent.protobuf, skynet.tostring(msg, sz), sz)
		end,
		dispatch = function (session, source, ...)
			local msghead, msgbody = ... 
			if msghead ~= nil then
				Agent:process(session, source, "client", msghead.msgname, msgbody)
			else
				filelog.sys_error(filename .. " line145 Agent:process_client_message  recive invalid client msg")
			end
			Agent.last_heart_time = timetool.get_time()
		end, 
	}

	skynet.dispatch("lua", function (session, source, msgname, ...)
        if msgname == "simulate_client" then    --模拟客户端发送的消息
            Agent:process(session, source, "client", ...)
        else
		    Agent:process(session, source, "lua", msgname, ...)
        end
	end)

	-- 提前把一些设置的变量获取，不用每次使用的是去获取
	agent_heart_timeout = configdao.get_common_conf("agent_heart_timeout")
	self.qiniu_access_key = configdao.get_common_conf("qiniu_access_key")
	self.qiniu_secret_key = configdao.get_common_conf("qiniu_secret_key")


	self.socket = require("skynet.socket")
	debug.getregistry().PROTOBUF_ENV = skynet.call(".pbcservice", "lua", "get_protobuf_env")
	self.protobuf = require "protobuf"
	self.decode = require "protopack"

	self:add_eventbyname("cmd", "agentmsg/agentcmd")
	self:add_eventbyname("hi", "agentmsg/hi")
	self:add_eventbyname("heart", "agentmsg/heart")
	self:add_eventbyname("getroleinfo", "agentmsg/getroleinfo")	
	self:add_eventbyname("gettablebycode", "agentmsg/gettablebycode")	
	self:add_eventbyname("updateroleinfo", "agentmsg/updateroleinfo")
	self:add_eventbyname("getpubtablelist", "agentmsg/getpubtablelist")
	self:add_eventbyname("getgameresult", "agentmsg/getgameresult")
	self:add_eventbyname("getqiniutoken", "agentmsg/getqiniutoken")
	self:add_eventbyname("gethrlconfigmsg", "agentmsg/gethrlconfigmsg")
	self:add_eventbyname("tablechat", "agentmsg/tablechat")
	self:add_eventbyname("getrank", "agentmsg/getrank")
	self:add_eventbyname("getidlerole", "agentmsg/getidlerole")
	self:add_eventbyname("tableinvite", "agentmsg/tableinvite")
	self:add_eventbyname("banksave", "agentmsg/banksave")
	self:add_eventbyname("bankwithdraw", "agentmsg/bankwithdraw")
	self:add_eventbyname("getmaillist", "agentmsg/getmaillist")
	self:add_eventbyname("getmailattach", "agentmsg/getmailattach")
	self:add_eventbyname("delmail", "agentmsg/delmail")
	self:add_eventbyname("gethallad", "agentmsg/gethallad")
    self:add_eventbyname("realnameauth", "agentmsg/realnameauth")
    self:add_eventbyname("phoneauth", "agentmsg/phoneauth")
    self:add_eventbyname("getverificationcode", "agentmsg/getverificationcode")
    self:add_eventbyname("setagencyrid", "agentmsg/setagencyrid")
    self:add_eventbyname("upvote", "agentmsg/upvote")
    self:add_eventbyname("downvote", "agentmsg/downvote")
    self:add_eventbyname("getpaychannel", "agentmsg/getpaychannel")
    self:add_eventbyname("getgamenodeinfo", "agentmsg/getgamenodeinfo")
    self:add_eventbyname("updategpsaddr", "agentmsg/updategpsaddr")
    self:add_eventbyname("getchargerecord", "agentmsg/getchargerecord")
    self:add_eventbyname("propexchange", "agentmsg/propexchange")
    self:add_eventbyname("turntable", "agentmsg/turntable")
    self:add_eventbyname("getturntableinfo", "agentmsg/getturntableinfo")
    self:add_eventbyname("getcoinpubtablelist", "agentmsg/getcoinpubtablelist")
    self:add_eventbyname("startgame", "agentmsg/startgame")
    self:add_eventbyname("relive", "agentmsg/relive")
    self:add_eventbyname("uploadscore", "agentmsg/uploadscore")
    self:add_eventbyname("useprop", "agentmsg/useprop")
    self:add_eventbyname("getknapsack", "agentmsg/getknapsack")
	self:add_eventbyname("changephonepassword", "agentmsg/changephonepassword")
    self:add_eventbyname("bindwithdrawaccount", "agentmsg/bindwithdrawaccount")
    self:add_eventbyname("getwithdrawrecord", "agentmsg/getwithdrawrecord")
    self:add_eventbyname("withdrawmoney", "agentmsg/withdrawmoney")
    self:add_eventbyname("httprequest", "agentmsg/httprequest")
    self:add_eventbyname("bindagencyrid", "agentmsg/bindagencyrid")
    self:add_eventbyname("bankpwdset", "agentmsg/bankpwdset")
    self:add_eventbyname("givemoney", "agentmsg/givemoney")
	self:add_eventbyname("getgivemoney", "agentmsg/getgivemoney")
	self:add_eventbyname("cancelgivemoney", "agentmsg/cancelgivemoney")
	self:add_eventbyname("getgiverecordlist", "agentmsg/getgiverecordlist")
	self:add_eventbyname("getgivedrawlist", "agentmsg/getgivedrawlist")
	self:add_eventbyname("getactivityaward", "agentmsg/getactivityaward")
	self:add_eventbyname("getactivityinfo", "agentmsg/getactivityinfo")
	self:add_eventbyname("openbank", "agentmsg/openbank")
	-- self:rever_dbstruct() -- 暂时不做这个
end

-- 反向得到数据库中 item->table 的对应关系
function Agent:rever_dbstruct()
	for k,v in pairs(dbstruct) do
		if k:sub(1,1) ~= "r" then
			-- 试验使用一下 goto，来控制循环，可以减少 if end 的层次
			goto no_rid_teble
		end
		for i,j in pairs(v.fields) do
			if 	i ~= "rid" and i ~= "uid" and i ~= "keyid" and i ~= "create_time" then
				if self.item2table[i] then
					-- 数据库有相同字段，abort
					-- print("==>> ABORT:DB has same filed -> field:" .. i .. " @table:"..k )
					-- skynet.abort()
				else
					self.item2table[i] = k
				end
			end
		end
		::no_rid_teble::  --不是role对应的数据表
	end	
end

function Agent:add_eventbyname(name, event)
	self.eventpool[name] = require(event)
	self.eventpool[name].service = Agent -- 这个是为了让下一层的处理命令的模块知道是谁来调用的，
end

function Agent:tick()
	if self.last_heart_time + agent_heart_timeout  < timetool.get_time() then
		--心跳超时给玩家做断线处理
		self:disconnect(self.client_fd, true)
	end  
end

function Agent:send_resmsgto_client(fd, msgname, msgbody, retcode, retstr)
	skynet.pcall(self.socket.write, fd, netpack.pack(self.decode.s2cpack(self.protobuf, msgname, retcode, retstr, msgbody)))
end

function Agent:create_session(conf)
	self.client_fd = conf.client
	self.gate_service = conf.gate
	self.watch_dog = conf.watchdog	
	self.last_heart_time = timetool.get_time()
	self.enter_time = self.last_heart_time
    self.clientver = conf.msgbody.clientver

	self.link_ip, self.port = string.match(conf.ipport, "(.+):(%d+)")
	self.isoffline = false
	self.state = EGateAgentState.GATE_AGENTSTATE_LOGINING
    self.rid = tonumber(conf.msgbody.rid) or 0
	local agencyrid = 0
	if conf.msgbody.agencyrid then
		agencyrid = tonumber(conf.msgbody.agencyrid)
	end

	skynet.fork(function()
		while true do
			skynet.sleep(1000)
			self:tick()
		end
	end)

	local status = skynet.pcall(skynet.call,self.gate_service, "lua", "forward", self.client_fd)
    if not status then
        self:exit_service()
        return false
    end

	return self:entergame(self.client_fd, tonumber(conf.msgbody.uid), tonumber(conf.msgbody.rid), agencyrid, conf.msgbody.reportip)
end

-- creat_session 成功后，立刻调用这个
function Agent:entergame(fd, uid, rid, agencyrid, reportip)
	local status,result 
	
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINING then
		return false
	end

	if reportip == "" or reportip == nil then
		self.ip = self.link_ip
	else
		self.ip = reportip
	end
	
	-- 这里如果 result[1] == 2 说明是新创建的玩家
	-- 在login的时候,已经创建了角色，所以这里不能主动去创建（预防开服断线重连，重新创建rid的情况）
	result = cluster.call("datanode", ".router", "select_rk", rid, "rs_info", true)
	if result[1] == 0 then
		self:send_resmsgto_client(fd, "entergameR", {}, -99, "must login again")
		return false
	end
	self.info = tabletool.deepcopy(result[2][rid])
	-- 如果这里传入agencyrid>0 本身 agencyrid ==0  按照花猪逻辑，就要更换成新的代理
	if agencyrid > 0 and self.info.agencyrid == 0 then
		self.info.agencyrid = agencyrid
		cluster.call("datanode", ".router", "update_rk", rid, "rs_info", 0, {agencyrid=agencyrid})
		local cmdstr = json.encode({msghead={msgname="bind_agencyrid"}, msgbody={uid=uid, aid = agencyrid, rid=rid}})
		redisdao.query_data(".gmmqredis", "rpush", "responsemq", cmdstr)
		skynet.send(".gamedatalog", "lua", "write", cmdstr)
	end

	result = cluster.call("datanode", ".router", "select_rk", rid, "rs_money")
	if result[1] == 0 then
		self:send_resmsgto_client(fd, "entergameR", {}, -99, "must login again")
		return false
	end
	self.money = tabletool.deepcopy(result[2][rid])

    --外网金豆初始为200
    if self.clientver ~= "0000" and result[1] == 2 and type(self.money) == "table" and self.money.diamond ~= 0 then
        cluster.call("datanode", ".router", "update_rk", rid, "rs_money", 0, {diamond = 0})
        self.money.diamond = 0
    end

    --获取其他信息
    result = cluster.call("datanode", ".router", "select_rk", rid, "rs_otherinfo")
	if result[1] == 0 then
		return false
	end
	self.info.upvoted_num = result[2][rid].upvoted_num or 0
    self.info.downvoted_num = result[2][rid].downvoted_num or 0
    self.info.gamedata = result[2][rid].gamedata or "{}"
    self.gamedata_list = json.decode(self.info.gamedata) or {}
    self.info.alipay_realname = result[2][rid].alipay_realname
    self.info.bankcard_realname = result[2][rid].bankcard_realname
    self.info.weixin_realname = result[2][rid].weixin_realname

    --保存群发邮件
    local massmail_list = result[2][rid].massmail_list or "{}"
    self.info.massmail_list = json.decode(massmail_list) or {}

	--获取头像框
	local logoframe = skynet.call(".knapsack", "lua", "getlogoframeid", rid, self.info.logoframe)
	self.info.logoframe = logoframe or 0

    --随机机器人ip,gps
    if self.info.level == 99 then
        local ipstr = "172."
        for i = 2,4 do
            if i == 2 then
                if base.get_random(1,2) == 1 then
                    ipstr = ipstr .. "16"
                else
                    ipstr = ipstr .. "31"
                end
            else
                local r = base.get_random(1,254) or 39
                ipstr = ipstr .. tostring(r)
            end
            if i < 4 then
                ipstr = ipstr .. "."
            end
        end
        self.ip = ipstr

        local gpsaddr = { state = 2}
        gpsaddr.latitude = base.get_random(10000,1000000) / 10000
        gpsaddr.longitude = base.get_random(10000,1000000) / 10000
        self.gps_addr = json.encode(gpsaddr)
    end

	-- 以下赋值只是为了调试
	-- print("lin253 -------  onlytest : set gamenode, table_index ---------")
	-- self.gamenode = "ddz"
	-- self.table_index = 1
	-- print("lin253 -------  onlytest : set gamenode, table_index ---------")

	-- 至此，玩家数据已经从data中装载进入了内存
	self.state = EGateAgentState.GATE_AGENTSTATE_LOGINED
	self.gamenode_config_ab = skynet.call(".router", "lua", "get_gamenode_config_ab")
	-- 从roletablestate ，一方面是写入用户onlinerids 另外可以看是否redis里面有玩家在桌信息
	-- 当gatenode重启的时候，在桌晚间信息是写入redis的
	local onlinestat = skynet.call(".roletablestate", "lua", "rid_entergame", self.rid, self.info.rolename, self.info.sex, self.info.logo, self.info.level)

	if onlinestat.nodename then
		self.gamenode = onlinestat.nodename
	else
		self.gamenode = ""
	end

    --匹配节点
    if onlinestat.match_gamenode then
		self.match_gamenode = onlinestat.match_gamenode
        self.match_tabletype = onlinestat.match_tabletype
	else
		self.match_gamenode = ""
	end

	if onlinestat.table_index then
		self.table_index = tonumber(onlinestat.table_index)
	else
		self.table_index = 0
	end

	self.uid = uid
	self.rid = rid
	-- return false
	local roleinfo = {
		rid = self.rid,
		rolename = self.info.rolename,
		level =  self.info.level,
		logo =  self.info.logo,
		sex =  self.info.sex,
		intro = self.info.intro,
		diamond = self.money.diamond,
		lock_diamond = self.money.lock_diamond,
		coin = self.money.coin,
        lock_coin = self.money.lock_coin,
		fangka = self.money.fangka,
		recommrid =  self.info.recommrid,
		agencyrid =  self.info.agencyrid,
		agencylevel =  self.info.agencylevel,
		ip_addr = self.ip,
        gps_addr = self.gps_addr,
        phonenum = self.info.phonenum,
        idtrue = tonumber(self.info.idtrue),
        logoframe = self.info.logoframe,
        alipayaccount = self.info.alipayaccount,
        weixinaccount = self.info.weixinaccount,
        bankcardaccount = self.info.bankcardaccount,
        bankcard_bankname = self.info.bankcard_bankname,
        alipay_realname = self.info.alipay_realname,
        bankcard_realname = self.info.bankcard_realname,
        weixin_realname = self.info.weixin_realname,
        realname = self.info.realname,
		pkgid = self.info.pkgid,
	}
	local response = {
		isreauth = 0,				
		servertime = timetool.get_time(),
		baseinfo = roleinfo,
		gamenode = self.gamenode,
		table_index = self.table_index,
	}
	if string.sub(response.gamenode, -4, -2) == "nod" then
		response.gamenode = string.sub(response.gamenode, 1, -5)
	end
	self:send_resmsgto_client(fd, "entergameR", response)
	
	-- 是否需要发新邮件通知红点
	status,result = skynet.pcall(cluster.call,"datanode", ".router", "select_rk", rid, "rs_readmail")
	if status and result then
        if result[1] > 0 and result[2][rid].last_sendtime > result[2][rid].last_gettime then
		    self:send_resmsgto_client(fd, "newmailN", {sendtime=result[2][rid].last_sendtime})
	    end
    else
        filelog.sys_error(rid,"get mail failed!",result)
    end
	local logstr = string.format("%d,%d,%s", EGateAction.ENTER, self.rid, self.ip)
	skynet.send(".gamedatalog", "lua", "write", logstr)

    --检测是否需要发送群发邮件
	self:check_massmail()
	
	--检测是否需要发送领取赠送通知
	local _,num = redisdao.query_data(".gmmqredis", "get", "unclaimednum:" .. self.rid)
	if num and tonumber(num) > 0 then
		self:send_resmsgto_client(fd, "givemoneyN", { })
	end
	
	return true
end

function Agent:reconnect(conf)
	if self.state == EGateAgentState.GATE_AGENTSTATE_LOGINING then
        filelog.sys_error(filename .." lien404 this agent is logining")
		return false
	end

	if self.state == EGateAgentState.GATE_AGENTSTATE_UNKNOW 
		or self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTING then
		filelog.sys_error(filename .." lien296 this agent is logouting")
		skynet.pcall(skynet.send, self.watch_dog, "lua", "cmd", "agentexit", self.client_fd, self.rid)
		return false
	elseif self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTED then
        filelog.sys_error(filename .." lien404 this agent is logouted")
		return false
	end 

	local status = skynet.pcall(skynet.call,self.gate_service, "lua", "forward", conf.client)
    if not status then
        filelog.sys_error(filename .." lien426 call forward failed!")
        return false
    end

	-- 这块代码暂时还不知当时为啥屏蔽了。
	-- if self.state == EGateAgentState.GATE_AGENTSTATE_UNKNOW 
	-- 	or self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTING then
	-- 	filelog.sys_error(filename .." line311 this agent is logouting")
	-- 	skynet.pcall(skynet.send, self.watch_dog, "lua", "cmd", "agentexit", self.client_fd, self.rid)
	-- 	return false
	-- elseif self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTED then
	-- 	return false
	-- end 

	--通知先前的设备玩家在其他设备上登陆
	if self.client_fd ~= nil then
		self:send_resmsgto_client(self.client_fd, "repeatentergameN", {rid = self.rid, uniquecode = conf.msg.uniquecode})
	end

	self.client_fd = conf.client
	self.gate_service = conf.gate
	self.watch_dog = conf.watchdog	
	self.last_heart_time= timetool.get_time()
	self.link_ip, self.port = string.match(conf.ipport, "(.+):(%d+)")
	self.isoffline = false
	self.state = EGateAgentState.GATE_AGENTSTATE_LOGINING
    
    --机器人不刷新ip
    local reportip = conf.msg.reportip
    if (self.info and self.info.level == 99) or reportip == "127.0.0.1" or link_ip == "127.0.0.1" then
        --not
    else
        if reportip == "" or reportip == nil then
		    self.ip = self.link_ip
	    else
		    self.ip = reportip
	    end
    end

	-- 如果info是ni，则需要从 datanode 里面获取用户的数据吗？
	if self.info == nil then
		local result 
        local rid = self.rid

		-- 这里如果 result[1] == 2 说明是新创建的玩家
		result = cluster.call("datanode", ".router", "select_rk", rid, "rs_info", true)
		if result[1] == 0 then
			return false
		end
		self.info = tabletool.deepcopy(result[2][rid])

		result = cluster.call("datanode", ".router", "select_rk", rid, "rs_money")
		if result[1] == 0 then
			return false
		end
		self.money = tabletool.deepcopy(result[2][rid])

        --获取其他信息
        result = cluster.call("datanode", ".router", "select_rk", rid, "rs_otherinfo", true)
	    if result[1] == 0 then
		    return false
	    end
	    self.info.upvoted_num = result[2][rid].upvoted_num or 0
        self.info.downvoted_num = result[2][rid].downvoted_num or 0
        self.info.gamedata = result[2][rid].gamedata or "{}"
        self.gamedata_list = json.decode(self.info.gamedata) or {}
        self.info.alipay_realname = result[2][rid].alipay_realname
        self.info.bankcard_realname = result[2][rid].bankcard_realname
        self.info.weixin_realname = result[2][rid].weixin_realname

        --保存群发邮件
        local massmail_list = result[2][rid].massmail_list or "{}"
        self.info.massmail_list = json.decode(massmail_list) or {}

		--头像框
		local logoframe = skynet.call(".knapsack", "lua", "getlogoframeid", rid, self.info.logoframe)
		self.info.logoframe = logoframe or 0
	end

	self.state = EGateAgentState.GATE_AGENTSTATE_LOGINED

	local roleinfo = {
		rid = self.rid,
		rolename = self.info.rolename,
		level =  self.info.level,
		logo =  self.info.logo,
		sex =  self.info.sex,
		diamond = self.money.diamond,
		lock_diamond = self.money.lock_diamond,
		lock_password = self.money.lock_password,
		coin = self.money.coin,
        lock_coin = self.money.lock_coin,
		fangka = self.money.fangka,
		recommrid =  self.info.recommrid,
		agencyrid =  self.info.agencyrid,
		agencylevel =  self.info.agencylevel,
		ip_addr = self.ip,
        gps_addr = self.gps_addr,
        phonenum = self.info.phonenum,
        idtrue = tonumber(self.info.idtrue),
        logoframe = self.info.logoframe,
        alipayaccount = self.info.alipayaccount,
        weixinaccount = self.info.weixinaccount,
        bankcardaccount = self.info.bankcardaccount,
        bankcard_bankname = self.info.bankcard_bankname,
        alipay_realname = self.info.alipay_realname,
        bankcard_realname = self.info.bankcard_realname,
        weixin_realname = self.info.weixin_realname,
        realname = self.info.realname,
		pkgid = self.info.pkgid,
	}
	local response = {
		isreauth = 0,				
		servertime = timetool.get_time(),
		baseinfo = roleinfo,
		gamenode = self.gamenode,
		table_index = self.table_index,
        match_gamenode = self.match_gamenode,
        match_tabletype = self.match_tabletype,
	}
	if string.sub(response.gamenode, -4, -2) == "nod" then
		response.gamenode = string.sub(response.gamenode, 1, -5)
	end

	self:send_resmsgto_client(self.client_fd, "entergameR", response)
	local logstr = string.format("%d,%d,%s", EGateAction.REENTER, self.rid, self.ip)
	skynet.send(".gamedatalog", "lua", "write", logstr)
	return true
end

-- socket断线后，调用这个, 用户掉线， 但不是用户离开
-- isheart ：是心跳包检测到socket断线，设置成true
function Agent:disconnect(fd, isheart)
	if isheart == nil then
		isheart = false
	end
	if fd ~= self.client_fd then
		return 
	end

	local logstr
	if isheart then
		logstr = string.format("%d,%d,%s,%d", EGateAction.HALLHEARTOUT, self.rid, self.ip, timetool.get_time() - self.enter_time)
	else
		logstr = string.format("%d,%d,%s,%d", EGateAction.DISCONNECT,   self.rid, self.ip, timetool.get_time() - self.enter_time)
	end
	skynet.send(".gamedatalog", "lua", "write", logstr)

    --不在桌内和匹配中直接结束agent
    --[[
	if (self.gamenode == "" or self.table_index <= 0)
    and self.match_gamenode == "" then
		self:agentexit()
		return
	end
    ]]
	
	-- 如果在桌子里面或匹配中，要给游戏节点发一个 disconnect
    local nodename = self.gamenode or ""
    if nodename == "" then
        nodename = self.match_gamenode or ""
        if nodename ~= "" then
            if self.gamenode_config_ab[nodename] then
                nodename = nodename .. "nod" .. self.gamenode_config_ab[nodename]
            else
                nodename = nodename .. "node"
            end
        end
    end

    if nodename ~= "" then
        local roledata = {info = self.info, money = self.money}
	    local isok,result = skynet.pcall(cluster.call, nodename, ".router", "disconnect", self.rid, roledata)
	    if not isok or result == false then
		    self:agentexit()
		    return
	    end
    end

	-- 如果玩家已经因为心跳超时掉线，则玩家退出
	if isheart and self.isoffline then
		self:agentexit()
		return
	end
 	
	--设置玩家掉线
	self.isoffline = true
	
	--通知主动关掉socket
	if isheart then
		skynet.pcall(skynet.send, self.watch_dog, "lua", "cmd", "heart_timeout", self.client_fd)
		logstr = string.format("%d,%d,%s,%d", EGateAction.GAMEHEARTOUT, self.rid, self.ip, timetool.get_time() - self.enter_time)
		skynet.send(".gamedatalog", "lua", "write", logstr)
	end
end

-- 只有当client主动关闭socket，参数才为true
-- 所以只会在agent[cmd][close]， 这里才调用agentexit(true) 
-- 普通的调用agentexit() ,会第二次进入agentexit(true)
--[[
	主动关闭流程：
	客户端 close socket， wsgate 会发起一个socket_close消息， 到GatenodeSocket.close
	然后到了longtcpmng.close_socket, 然后进入到了agent.disconnect, agent不在比赛，桌子的
	调用 agentexit(), 此时 is_clientclose 是nil，所以会call watchdog:agentexit()
	watchdog:agentexit()里面调用 longtcpmng.agentexit(fd, rid)-> 会调用 agent:close()
	这里面又有 agent:agentexit, agent:exit_service()

	agent 自己超时退出流程
]]
function Agent:agentexit(is_clientclose)
	print(filename .. " line438, Agent:agentexit, self.state=", self.state)
	if self.state == EGateAgentState.GATE_AGENTSTATE_UNKNOW 
		or self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTING
		or self.state == EGateAgentState.GATE_AGENTSTATE_LOGOUTED then
		return
	end

	--做一些退出前处理
	self.state = EGateAgentState.GATE_AGENTSTATE_LOGOUTING

	--2.更新玩家的在线状态数据 send
	-- 离桌
	-- TODO :重置游戏在线状态信息
	self.state = EGateAgentState.GATE_AGENTSTATE_LOGOUTED
	
	--3.通知gatesvrd  agentexit
	if not is_clientclose then
		skynet.pcall(skynet.send, self.watch_dog, "lua", "cmd", "agentexit", self.client_fd, self.rid)
	end
	skynet.send(".roletablestate", "lua", "rid_exit", self.rid)
	local logstr = string.format("%d,%d,%s,%d", EGateAction.EXITCELAR, self.rid, self.ip, timetool.get_time() - self.enter_time)
	skynet.send(".gamedatalog", "lua", "write", logstr)
	--self:clear()
end

function Agent:clear()
	--玩家基本信息
	self.uid = 0
	self.rid = 0
	self.info = nil
	self.money = nil
	self.enter_time = 0
end

-- agent 没有 tcpmng , service_manager 所有 exit_service 就只需要简化成 skynet.exit() 退出自己的服务
function Agent:exit_service( ... )
	filelog.sys_info(filename .. "-------------------- Agent:exit_service --------------------")
	skynet.exit()
end

-- agent 上的一些对外逻辑接口, 登录状态能操作
function Agent:notify_client(msgname, msgbody, retcode, retstr)
	if self.client_fd and self.state == EGateAgentState.GATE_AGENTSTATE_LOGINED then
		self:send_resmsgto_client(self.client_fd, msgname, msgbody, retcode, retstr)
	end
end

-- 一个node之内各个service之间的调用,归属在cmd总命令下
-- 这里使用 myroleinfo 是为了和 socket来的 getroleinfo 命令做一下区分，其实是可以同名的
function Agent:myroleinfo()
	return {
			rid = self.rid,
			rolename = self.info.rolename,
			level =  self.info.level,
			logo =  self.info.logo,
			sex =  self.info.sex,
			intro = self.info.intro,
			diamond = self.money.diamond,
			lock_diamond = self.money.lock_diamond,
			lock_password = self.money.lock_password,
			coin = self.money.coin,
            lock_coin = self.money.lock_coin,
			fangka = self.money.fangka,
            phonenum = self.info.phonenum,
            idtrue = self.info.idtrue,
            ip_addr = self.ip,
            gps_addr = self.gps_addr,
            upvoted_num = self.info.upvoted_num,
            downvoted_num = self.info.downvoted_num,
            logoframe = self.info.logoframe,
            alipayaccount = self.info.alipayaccount,
            weixinaccount = self.info.weixinaccount,
            bankcardaccount = self.info.bankcardaccount,
            bankcard_bankname = self.info.bankcard_bankname,
            alipay_realname = self.info.alipay_realname,
            bankcard_realname = self.info.bankcard_realname,
            weixin_realname = self.info.weixin_realname,
            realname = self.info.realname,
			pkgid = self.info.pkgid,
		}
end

-- 游戏节点重启通知在线角色删除与之关联的节点信息并退出游戏节点，避免信息不对等，导致断线重连等没有响应
function Agent:gamenode_init(nodename)
	if self.gamenode ~= "" and self.gamenode == nodename then
		self:send_resmsgto_client(self.client_fd, string.match(self.gamenode, "(.+)nod%a") .. ".leavetableN", {rid = self.rid})
		skynet.call(".roletablestate", "lua", "rid_leave_table", self.rid, self.gamenode, self.table_index)
		self.gamenode = ""
		self.table_index = 0
		self.match_gamenode = ""
    	self.match_tabletype = nil

		local response = {
			isreauth = 0,				
			servertime = timetool.get_time(),
			-- baseinfo = roleinfo,
			gamenode = self.gamenode,
			table_index = self.table_index,
		}
		self:send_resmsgto_client(self.client_fd, "entergameR", response)
	end
end

-- 用cs包裹起来
-- comment : 至少包含节点名称， 节点唯一trade_id， 修改原因编码 reason_id, 上层节点comment:upper
-- 			如果是桌子来的，要有 table_uuid
-- {formnode, tradeid, reasonid, upper}
-- 如果是多节点调用 upper 就是上层的的comment结构
-- 这里单独拎出来，只修改 money相关数据库，全部在rs_money 一张表中
-- 暂时只考虑修改coin， fangka （正式服会关闭钻石修改）

-- 只有rs_money表里面字段用这个了，这里不用通知游戏服
local function real_change_money(self, itemvalues, comment)
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
		-- todo : 应该插入 harverst表，等登录成功后，可以显式的点击收割
        filelog.sys_error("agent state is error!",self.rid, self.state, itemvalues, comment)
		return nil
	end
	-- 先修改内存数据，再去调用datanode的update
	local setargs = {}
	for item, value in pairs(itemvalues) do
		if item ~= "fangka" and item ~= "diamond" and item ~= "lock_diamond" and item ~= "coin" and item ~= "lock_coin" then
            filelog.sys_error("value type error!",self.rid, self.item, itemvalues, comment)
			return false
		end
		-- 数据修改有有问题，退出，这个请求都不生效
		if (self.money[item] + value) < 0  then
			-- todo :打错误日志
            filelog.sys_error("money value error! sum < 0",self.rid, value, itemvalues, comment)
			return false
		end

		setargs[item] = self.money[item] + value
	end

	local status, result = skynet.pcall(cluster.call, "datanode", ".router", "update_rk", self.rid, "rs_money", 0, setargs)
	if (status == true and result[1] == 1 and result[2] == true) then
		-- 重要数据， 等数据修改成功后， 再修改内存数据，然后再通知客户端
        if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
            filelog.sys_error("agent state has change !!!",self.rid, self.state)
		    return false
	    end
     
		for item, value in pairs(itemvalues) do
			self.money[item] = setargs[item]
		end

		-- 通知客户端，因为 optionl 为0 ，不发通知，这个是不对的，所以要用强制
		local msgbody = {}
		msgbody.rid = self.rid
		msgbody.diamond = self.money.diamond
		msgbody.lock_diamond = self.money.lock_diamond
		msgbody.fangka = self.money.fangka
        msgbody.coin = self.money.coin
        msgbody.lock_coin = self.money.lock_coin
		self:notify_client("changemoneyN",  {rolemoney=msgbody})
		if comment then
			-- 记录日志
		end
		return setargs
	else
		-- todo :怎么打错误日志，而且这块是比较严重的，涉及到数据回滚的问题
        filelog.sys_error("update database data failed!",self.rid, itemvalues, comment)
		return false
	end
end

function Agent:change_money(itemvalues, comment)
	return  cs(real_change_money, self, itemvalues, comment)
end

-- 只充值钻石， 需要通知游戏节点
function Agent:charge_diamond(value, comment, router)
	-- 处在一个登录的中间状态，确实需要好好斟酌处理一下
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
		--按离线充值处理
        filelog.sys_error("agent state is error!",self.rid, self.state, value, comment)
        return skynet.call(".offlinerole", "lua", "charge_diamond", self.rid, value, comment)
	end
	local beginvalue = self.money.diamond
	local setargs = self:change_money({diamond=value})
	local endvalue = self.money.diamond
	if setargs then
		self:notify_client("harvestdiamondN", {value=value}) -- 客户端弹窗
		if self.gamenode ~= "" then
			pcall(cluster.send, self.gamenode, ".router", "rid_change_diamond", self.rid, beginvalue, value, endvalue)
		end
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
		if not comment then comment = {} end
		local logstr 
		if router == "mail" then
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", EDiamondChange.MAIL, self.rid, self.info.level, value, 0, 0, beginvalue, endvalue, self.money.lock_diamond, comment.xxx, comment.mailkeyid)
		elseif tonumber(comment.agencylevel) == 1 then
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", EDiamondChange.AGENCY_CHARGE, self.rid, self.info.level, value, 0, 0, beginvalue, endvalue, self.money.lock_diamond, comment.xxx, comment.tradeid)
        else
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", comment.change_type or EDiamondChange.CHARGE, self.rid, self.info.level, value, 0, 0, beginvalue, endvalue, self.money.lock_diamond, comment.xxx, comment.tradeid)
		end
		skynet.send(".diamondlog", "lua", "write", logstr)
		
		--充值后，刷新打码值
		if comment.charge_type == EChargeType.ECHARGE_TYPE_AGENCY
		or comment.charge_type == EChargeType.ECHARGE_TYPE_AGENCY_PRE then
			--代理后台充值，不算充值
		elseif comment.charge_type ~= nil then
			self.money.base_chipvalue = self.money.base_chipvalue + value
			self.money.limit_chipvalue = self.money.limit_chipvalue + value
		end
		
		return setargs.diamond
	else
        if setargs == nil and self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
            return skynet.call(".offlinerole", "lua", "charge_diamond", self.rid, value, comment)
	    else
		    return false
        end
	end
end

-- 只充值房卡
function Agent:charge_fangka(value, comment, router)
	-- 处在一个登录的中间状态，确实需要好好斟酌处理一下
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
        filelog.sys_error("agent state is error!",self.rid, self.state, value, comment)
		return skynet.call(".offlinerole", "lua", "charge_fangka", self.rid, value, comment)
	end
	local beginvalue = self.money.fangka
	local setargs = self:change_money({fangka=value})
	local endvalue = self.money.fangka
	if not setargs then
        if setargs == nil and self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
		    return skynet.call(".offlinerole", "lua", "charge_fangka", self.rid, value, comment)
	    else
		    return false
        end
	end
	self:notify_client("messagetipsN", {tips="获得房卡" .. value .. "张"}) -- 客户端tips
	if not comment then comment = {} end
	local logstr = string.format("%d,%d,%s,%d,%d,%d", EFangKaChange.ADDFANGKA, self.rid, comment.table_tradeid, value, beginvalue, endvalue)
	skynet.send(".fangkalog", "lua", "write", logstr)
	return setargs.fangka
end

-- 改变道具数量
function Agent:change_prop(itemvalues, comment)
    local prop_id = tonumber(itemvalues.prop_id)
    local value = tonumber(itemvalues.value)

    if not prop_id or not prop_id then
        return false
    end

    --获得道具配置
    local curitem = skynet.call(".knapsack", "lua", "getpropconf", prop_id)
    if not curitem then
        return false
    end

    local updateitem = { id = prop_id}
    local kid = math.floor(prop_id / 100)
    if kid == PropKind.HEADFRAME then   --头像框,增加有效时间
        updateitem.value = value * curitem.effecttime * 24 * 3600
    elseif kid == PropKind.INTERPROP or kid == PropKind.VIPCARD then    --会员卡 互动表情，增加数量
        updateitem.value = value
    elseif kid == PropKind.GIFTSPACKAGE then
    	updateitem.value = value
    else
        return false
    end

    --更新
    local is_ok = skynet.call(".knapsack", "lua", "updateknapsack", self.rid, {[1] = updateitem})
    if not is_ok then
        return false
    end

    --通知玩家,背包信息改变
    local status, propitem = skynet.pcall(skynet.call, ".knapsack", "lua", "getpropbyid", self.rid, prop_id)
    if status then
        if not propitem and value < 0 then
            propitem = { id = prop_id, value = 0}
        end
        local updatelist = { [1] = propitem }
        local change_notice = { change_prop_listjson = json.encode(updatelist) }
        self:notify_client("changepropN", change_notice)
        return curitem
    end
    
    return false
end

-- 只充值道具
function Agent:charge_prop(prop_id, value, comment, router)
	-- 处在一个登录的中间状态，确实需要好好斟酌处理一下
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
		--按离线充值处理
        filelog.sys_error("agent state is error!",self.rid, self.state, prop_id, value, comment)
        return skynet.call(".offlinerole", "lua", "charge_prop", self.rid, prop_id, value, comment)
	end
	
    local result = self:change_prop({prop_id = prop_id, value = value})
    if result then
        local logstr = ""
        if router == "mail" then
			logstr = string.format("%d,%d,%d,%d,%s", EGateAction.MAILGETPROP, self.rid, prop_id or 0, value or 0, comment.mailkeyid)
		elseif router == "usegiftpackage" then
			logstr = string.format("%d,%d,%d,%d,%s", EGateAction.USE_GIFTPACKAGE, self.rid, prop_id or 0, value or 0, comment.giftkey)
		else
            logstr = string.format("%d,%d,%d,%d,%s", EGateAction.CHARGEPROP, self.rid, prop_id or 0, value or 0, comment.table_tradeid)
        end
	    skynet.send(".gamedatalog", "lua", "write", logstr)
        self:notify_client("messagetipsN", {tips="获得" .. result.descript .. "（" .. value .. "个）"}) -- 客户端tips
    else
        filelog.sys_error("charge prop error!",self.rid, prop_id, value, comment)
        return false
    end

    return true
end

-- 扣除道具
function Agent:reduce_prop(prop_id, value, comment)
	if value >= 0 then
        filelog.sys_error("error, value <= 0 !",self.rid, prop_id, value, comment)
	    return false
	end

    local result = self:change_prop({prop_id = prop_id, value = value})
    if not result then
        filelog.sys_error("reduce prop error!",self.rid, prop_id, value, comment)
        return false
    end
	
    return true
end

--扣服务费，valuestruct={beginvalue, value, endvalue}, 这里的value是正值
function Agent:game_sub_fuwufei(valuestruct, rid_values, comment, nodename)
	return self:game_sub_diamond(valuestruct, rid_values, comment, nodename)
end

--扣服务费，valuestruct={beginvalue, value, endvalue}, 这里的value是正值
function Agent:game_sub_diamond(valuestruct, rid_values, comment, nodename)
	-- 先校验 beginvalue - value = endvalue <== 注意这里要 减 value
	local value = valuestruct.value

	if value == nil or value <0 or (self.money.diamond - value) < 0  then
		-- todo :打错误日志
        filelog.sys_error("------------game_sub_fuwufei error",self.rid, self.ip, self.money.diamond, rid_values, comment)
		return false
	end
	local bd = self.money.diamond
	local setargs = self:change_money({diamond = -value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
        local logstr = nil
		if comment.is_fangfei then
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.FANGFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
        elseif nodename == "wzq" then
		    logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.WZQFUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
        elseif nodename == "xq" then
		    logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.XQFUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
        else
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.WZQFUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
        end
		skynet.send(".diamondlog", "lua", "write_file_redis", logstr, self.rid)
		return setargs.diamond
	else
		return false
	end
end

--扣服务费，valuestruct={beginvalue, value, endvalue}, 这里的value是正值
function Agent:game_sub_coin(valuestruct, rid_values, comment, nodename)
	-- 先校验 beginvalue - value = endvalue <== 注意这里要 减 value
	local value = valuestruct.value

	if value == nil or value <0 or (self.money.coin - value) < 0  then
		-- todo :打错误日志
        filelog.sys_error("------------game_sub_coin error",self.rid, self.ip, self.money.coin, rid_values, comment)
		return false
	end
	local bd = self.money.coin
	local setargs = self:change_money({coin = -value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
		local logstr = nil
        if nodename == "wzq" then
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.WZQFUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
		elseif nodename == "xq" then
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.XQFUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
		else
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.FUWUFEI, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
        end
        skynet.send(".coinlog", "lua", "write", logstr)
		return setargs.coin
	else
		return false
	end
end

-- 游戏结算，valuestruct{beginvalue, value, endvalue, rolename}, 这里的value可能是负数，就是表示扣费
-- 增加一个半场扣费标记， 如果是已经中途扣费结算过了，这里只记录战绩，不真实扣费，也不记录
-- 同时记录钻石场的战绩，（一局一记录，需要角色名）
-- comment 结构 {table_uuid, table_tradeid}
-- tableobj.uuid = attachconf.nodename .. "_" .. attachconf.code .. "_" .. attachconf.index .. "_" .. now
-- table_tradeid, 游戏服扣除的唯一条目（对账还是需要的）
function Agent:game_balance_diamond(valuestruct, rid_values, comment, nodename, is_cointable)
	-- 先校验 beginvalue + value = endvalue  <== 注意这里要 加 value
	local returnvalue = 0
	if valuestruct.had_half == nil then
		local value = valuestruct.value
		if value == nil then
			-- todo :打错误日志
            filelog.sys_error("------------game_balance_diamond error:value nil",self.rid, rid_values, comment)
			return false
		end
		if (self.money.diamond + value) < 0  then
			-- todo :打错误日志
            filelog.sys_error("------------game_balance_diamond error,sum < 0 ",self.rid, self.money.diamond, rid_values, comment)
			value = 0 - self.money.diamond
		end		
		local bd = self.money.diamond
		local setargs = self:change_money({diamond = value})
		if not setargs then
			return false
		end
		returnvalue = setargs.diamond
		local logstr
		if nodename == "wzq" then
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.WUZIQI,  self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
		elseif nodename == "xq" then
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.XIANGQI,  self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
        else
			logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.BALANCE, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
		end
		skynet.send(".diamondlog", "lua", "write", logstr)
	end

	if not is_cointable then
	    -- 记录钻石战绩
	    --skynet.send('.gameresult', 'lua', 'diamond_result', self.rid, rid_values, comment)
	end

    --记录游戏数据
    comment = comment or {}
    self:save_gamedata(nodename, valuestruct.value, comment.is_matchtable, valuestruct)

    --记录今日输赢
    skynet.send(".roletablestate", "lua", "write_winorloserecord", self.rid, valuestruct.value, nodename)

	return returnvalue
end

-- 中途离场的时候，直接扣钻石，但战绩要等最后完结时刻，结算时才写入
function Agent:game_half_diamond(valuestruct, rid_values, comment)
	local value = valuestruct.value
	if value == nil then
		-- todo :打错误日志
		filelog.sys_error("------------game_half_diamond error:value nil",self.rid, rid_values, comment)
		return false
	end
	if (self.money.diamond + value) < 0  then
		-- todo :打错误日志
		filelog.sys_error("------------game_half_diamond error,sum < 0 ",self.rid, self.money.diamond, rid_values, comment)
		value = 0 - self.money.diamond
	end		
	local bd = self.money.diamond
	local setargs = self:change_money({diamond = value})
	if not setargs then
         filelog.sys_error("------------game_half_diamond error",self.rid, self.money.diamond, rid_values, comment)
		return false
	end
	local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.HALF, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
    skynet.send(".diamondlog", "lua", "write",logstr)
	return setargs.diamond
end

-- 游戏结算，valuestruct{beginvalue, value, endvalue, rolename}, 这里的value可能是负数，就是表示扣费
-- 增加一个半场扣费标记， 如果是已经中途扣费结算过了，这里只记录战绩，不真实扣费，也不记录
-- 同时记录钻石场的战绩，（一局一记录，需要角色名）
-- comment 结构 {table_uuid, table_tradeid}
-- tableobj.uuid = attachconf.nodename .. "_" .. attachconf.code .. "_" .. attachconf.index .. "_" .. now
-- table_tradeid, 游戏服扣除的唯一条目（对账还是需要的）
function Agent:game_balance_coin(valuestruct, rid_values, comment, nodename)
	-- 先校验 beginvalue + value = endvalue  <== 注意这里要 加 value
	local returnvalue = 0
	if valuestruct.had_half == nil then
		local value = valuestruct.value
		if value == nil then
			-- todo :打错误日志
            filelog.sys_error("------------game_balance_coin error:value nil",self.rid, rid_values, comment)
			return false
		end
		if (self.money.coin + value) < 0  then
			-- todo :打错误日志
            filelog.sys_error("------------game_balance_coin error,sum < 0 ",self.rid, self.money.coin, rid_values, comment)
			value = 0 - self.money.coin
		end		
		local bd = self.money.coin
		local setargs = self:change_money({coin = value})
		if not setargs then
			return false
		end

		returnvalue = setargs.coin
        local logstr = nil
		if nodename == "wzq" then
		    logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.WUZIQI, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
        elseif nodename == "xq" then
		    logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.XIANGQI, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
        else
            logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.BALANCE, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
        end
		skynet.send(".coinlog", "lua", "write", logstr)
	end

    --记录战绩
    skynet.send('.gameresult', 'lua', 'coin_result', self.rid, rid_values, comment)

	return returnvalue
end

-- 中途离场的时候，直接扣金豆，但战绩要等最后完结时刻，结算时才写入
function Agent:game_half_coin(valuestruct, rid_values, comment)
	local value = valuestruct.value
	if value == nil then
		-- todo :打错误日志
		filelog.sys_error("------------game_half_coin error:value nil",self.rid, rid_values, comment)
		return false
	end
	if (self.money.coin + value) < 0  then
		-- todo :打错误日志
		filelog.sys_error("------------game_half_coin error,sum < 0 ",self.rid, self.money.coin, rid_values, comment)
		value = 0 - self.money.coin
	end		
	local bd = self.money.coin
	local setargs = self:change_money({coin = value})
	if not setargs then
         filelog.sys_error("------------game_half_coin error",self.rid, self.money.coin, rid_values, comment)
		return false
	end

	local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.HALF, self.rid, self.info.level, value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
    skynet.send(".coinlog", "lua", "write",logstr)

	return setargs.coin
end

--扣房卡valuestruct={begin, value, endvalue}, 这里的value是正值
function Agent:game_sub_fangka(valuestruct,rid_values, comment)
	-- 先校验 beginvalue - value = endvalue <== 注意这里要 减 value
	local value = valuestruct.value
	if value == nil or value <0 or (self.money["coin"] - value) < 0  then
		-- todo :打错误日志
         filelog.sys_error("------------game_sub_fangka error",rid_values, comment)
		return false
	end
    local bd = self.money.coin
	local setargs = self:change_money({coin = -value})
	if setargs then
        -- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始房卡值，后来房卡值， table_uuid， start_time, table_tradeid
		local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.SUBFANGKA, self.rid, self.info.level, 0-value, 0, 0,bd,self.money.coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
		skynet.send(".coinlog", "lua", "write", logstr)
		return setargs.coin
	else
		return false
	end
end

-- 记录房卡积分战绩，结构等同 game_balance_diamond，里面的 beginvalue, endvalue 可以不要，因为beginvalue 都是0
function Agent:game_balance_fangka(valuestruct, rid_values, comment)
	local value = valuestruct.value
	-- 记录房卡积分战绩
	skynet.send('.gameresult', 'lua', 'fangka_result', self.rid, rid_values, comment)
	return true
end

-- 这里只是处理lua来的cmd的修改消息，而且如果是涉及到baseinfo的字段，还需要广播给客户端
-- 只有不同的值，才去修改
function Agent:updateroleinfo(itemvalues, comment)
	if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then return false end

	local setargs = {}
	local doset = false
	local notifyargs = {}
	local donotifyfields = { rolename=1, level=1, agencyrid=1, agencylevel=1}
	for k,v in pairs(itemvalues) do
		if self.info[k] ~= v then 
			setargs[k] = v
			doset = true
			if donotifyfields[k] then
				notifyargs[k] = v
				notifyargs.rid = self.rid -- 通知客户端， roleinfo 结构中 rid 是必须的，正好可以拿来做标志位
			end
		end
	end
	if doset then
		self.eventpool.updateroleinfo.process(self.rid, setargs)
	end
	if notifyargs.rid then
		self:notify_client("changebaseinfoN",  {baseinfo=notifyargs})
	end

    return true
end

-- 银行存钱
-- moneytype 1房卡 2金豆 3金币
function Agent:banksave(moneytype, value)
    if self.gamenode ~= "" then
        filelog.sys_error("------------banksave error",self.rid, self.ip, self.money.diamond ,value)
        return {retcode=-2, retstr="游戏内不能存钱 "}
    end

    local moneyname = ""
    local lockmoney = ""
    if moneytype == 2 then   --金豆
        moneyname = "diamond"
        lockmoney = "lock_diamond"
    elseif moneytype == 3 then  --金币
        moneyname = "coin"
        lockmoney = "lock_coin"
    else
        return {retcode = -2, retstr = "无效的参数"}
    end

	if value <=0 then
		return {retcode=-1, retstr="存入金额必须大于0"}
	end
	if value > self.money[moneyname] then
		return {retcode=-2, retstr="携带金额不足"}
	end

	local beginvalue = self.money[moneyname]
	local beginbank = self.money[lockmoney]
	local setargs = self:change_money({[moneyname] = -value, [lockmoney] = value})
	local endvalue = self.money[moneyname]
	if setargs then		
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， beginbank, nil
        if moneyname == "diamond" then
            local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", EDiamondChange.BANK, self.rid, self.info.level, 0-value, 0, 0, beginvalue, endvalue,self.money[lockmoney], beginbank, nil)
            skynet.send(".diamondlog", "lua", "write", logstr)
        else
            local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", ECoinChange.BANK, self.rid, self.info.level, 0-value, 0, 0, beginvalue, endvalue,self.money[lockmoney], beginbank, nil)
            skynet.send(".coinlog", "lua", "write", logstr)
        end
		
		return {}
	else
		return false
	end

end

-- 银行取钱
-- moneytype 1房卡 2金豆 3金豆
function Agent:bankwithdraw(moneytype, value)
	if value <=0 then
		return {retcode=-1, retstr="取出金额必须大于0"}
	end

    local moneyname = ""
    local lockmoney = ""
    if moneytype == 2 then    --金豆
        moneyname = "diamond"
        lockmoney = "lock_diamond"
    elseif moneytype == 3 then   --金币
        moneyname = "coin"
        lockmoney = "lock_coin"
    else
        return {retcode = -2, retstr = "无效的参数"}
    end

	if value > self.money[lockmoney] then
		return {retcode=-2, retstr="保险箱金额不足"}
	end

	local beginvalue = self.money[moneyname]
	local beginbank = self.money[lockmoney]
	local setargs = self:change_money({[moneyname] = value, [lockmoney] = -value})
	local endvalue = self.money[moneyname]
	if setargs then
		if self.gamenode ~= "" then
            local msgname = "rid_change_" .. moneyname
			skynet.pcall(cluster.send, self.gamenode, ".router", msgname, self.rid, beginvalue, value, endvalue)
		end		
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， beginbank, nil
        if moneyname == "diamond" then
		    local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", EDiamondChange.BANK, self.rid, self.info.level, value, 0, 0, beginvalue, endvalue,self.money.lock_diamond, beginbank, nil)
            skynet.send(".diamondlog", "lua", "write", logstr)
        else
            local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s", ECoinChange.BANK, self.rid, self.info.level, value, 0, 0, beginvalue, endvalue,self.money.lock_coin, beginbank, nil)
            skynet.send(".coinlog", "lua", "write", logstr)
        end
		
		return {}
	else
		return false
	end

end

-- 玩家进入桌子后，给gata发入桌消息，这样agent可以设置玩家信息
function Agent:enter_table(rid, gamenode, table_index)
	-- if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
	-- 	return -2 -- 玩家online，但没登录成功
	-- end
	-- if self.gamenode ~= "" or self.table_index > 0 then 
	-- 	return -3 -- 玩家已经在桌子里了
	-- end
	self.gamenode = gamenode
	self.table_index = table_index
	skynet.call(".roletablestate", "lua", "rid_into_table", rid, gamenode, table_index)
	return 1
end

-- 玩家离开桌子后，给gata发离桌消息，这样agent可以设置玩家信息
-- todo
-- 离桌的时候，发出离桌时所携带的 ev_diamond， 便于和agent身上diamond比较
-- 如果不同，则需要记录报警日志(如果 ev_diamond,则不需要比较)
-- 有一点注意，如果是桌子删除，离线玩家， 还在桌子中， 删除桌子动作(del_table_code)，会给这些玩家发rid_level_table
function Agent:leave_table(rid, gamenode, table_index, ev_diamond)
	-- if self.state ~= EGateAgentState.GATE_AGENTSTATE_LOGINED then
	-- 	return -2 -- 玩家online，但没登录成功
	-- end
	
	-- if self.gamenode == "" and self.table_index == 0 then 
	-- 	return -3 -- 玩家已经不在桌子里了
	-- end
	-- if gamenode ~= self.gamenode then 
	-- 	return -4 -- 游戏不对
	-- end
	-- if table_index ~= self.table_index then 
	-- 	return -5 -- 桌子序号不对
	-- end
	self.gamenode = ""
	self.table_index = 0
	skynet.send(".roletablestate", "lua", "rid_leave_table", rid, gamenode, table_index)
	return 1
end

-- 玩家开始匹配
function Agent:start_match(rid, gamenode, tabletype)
	self.match_gamenode = gamenode
    self.match_tabletype = tabletype or 1
	skynet.send(".roletablestate", "lua", "rid_start_match", rid, gamenode, tabletype)
	return 1
end

--玩家结束匹配
function Agent:end_match(rid, gamenode, tabletype)
	self.match_gamenode = ""
    self.match_tabletype = nil
	skynet.send(".roletablestate", "lua", "rid_end_match", rid, gamenode, tabletype)
	return 1
end

--增加被赞数
function Agent:inc_upvoted_num(rolename)
    --不是很重要的数据，直接修改
    self.info.upvoted_num = (self.info.upvoted_num or 0) + 1
    cluster.send("datanode", ".router", "update_rk", self.rid, "rs_otherinfo", nil, {upvoted_num = self.info.upvoted_num})

    --通知玩家
    if not rolename or rolename == "" then
        rolename = "羊宝宝"
    end
    local tips = rolename .. "给了你一个大大的赞"
    self:notify_client("messagetipsN", {tips = tips})

    --通知玩家，基本信息改变
    self:notify_client("changebaseinfoN",{baseinfo = {rid = self.rid, upvoted_num = self.info.upvoted_num}})
end

--增加被踩数
function Agent:inc_downvoted_num(rolename)
    --不是很重要的数据，直接修改
    self.info.downvoted_num = (self.info.downvoted_num or 0) + 1
    cluster.send("datanode", ".router", "update_rk", self.rid, "rs_otherinfo", nil, {downvoted_num = self.info.downvoted_num})

    --通知玩家，基本信息改变
    self:notify_client("changebaseinfoN",{baseinfo = {rid = self.rid, downvoted_num = self.info.downvoted_num}})
end

--过滤敏感词
function Agent:filterSensitiveWords(str, words)
    --从reidis读取敏感词
    if not words then
        local result, data = redisdao.query_data(".gmmqredis", "get", "sensitiveWords")
        if result and data then
            words = json.decode(data) or { }
        end
    end

    --检测是否有特殊字符
    local function checkSpecialChar(word)
        local specialChar = {
            ['.']=true,['%']=true,['+']=true,['-']=true,
            ['*']=true,['?']=true,['[']=true,['^']=true,
            ['(']=true,[')']=true,['$']=true,
        }

        local tArray = string.gmatch(word, ".[\128-\191]*")
        local contentArray = {}
        for w in tArray do  
            table.insert(contentArray,w)
        end

        local ck = {}
        for i=1,#contentArray do
            local v = contentArray[i]
            if specialChar[v] == true then
                table.insert(ck,'%')
            end
            table.insert(ck,v)
        end

        local result=''
        for i,v in ipairs(ck) do
            result = result..v
        end

        return result
    end
    -----------------------

    if type(str) ~= "string" or type(words) ~= "table" then
        return str
    end

    --整体过滤一次
    for _,w in pairs(words) do
        local repl = ""
        local iter_func = string.gmatch(w, ".[\128-\191]*")
        for s in iter_func do
            repl = repl .. "*"
        end

        w = checkSpecialChar(w)
        str = string.gsub(str,w,repl)
    end

    --仅保留汉字过滤一次
    local indexs = { }
    local arr = string.gmatch(str, ".[\128-\191]*")
    local tmpstr = ""
    local cnt = 0
    local idx = 0
    for s in arr do
        idx = idx + 1
        if string.find(s, "[\228-\238]") then
            cnt = cnt + 1
            tmpstr = tmpstr .. s
            indexs[idx] = cnt
        else
           indexs[idx] = s
        end
    end

    --filelog.print("---str",tmpstr,indexs)
    
    for _,w in pairs(words) do
        local repl = ""
        local iter_func = string.gmatch(w, ".[\128-\191]*")
        for s in iter_func do
            repl = repl .. "*"
        end

        w = checkSpecialChar(w)
        tmpstr = string.gsub(tmpstr,w,repl)
    end

    --恢复字符串
    local strs = { }
    arr = string.gmatch(tmpstr, ".[\128-\191]*")
    for s in arr do
        table.insert(strs,s)
    end

    str = ""
    for i = 1,idx do
        if type(indexs[i]) == "number" then
            indexs[i] = strs[indexs[i]] or ""
        end
        str = str .. indexs[i]
    end

    return str
end

--开奖
function Agent:open_jackpot_diamond(valuestruct, rid_values, comment)
	local value = valuestruct.value

	if value == nil or value < 0 or (self.money.diamond + value) < 0 then
		-- todo :打错误日志
        filelog.sys_error("------------open_jackpot_diamond error",self.rid, self.ip, self.money.diamond, valuestruct, comment)
		return false
	end
	local bd = self.money.diamond
	local setargs = self:change_money({diamond = value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
		local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.JACKPOT, self.rid, self.info.level, value, 0, 0,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
		skynet.send(".diamondlog", "lua", "write", logstr, self.rid)
		return setargs.diamond
	else
		return false
	end
end

--开奖
function Agent:open_jackpot_coin(valuestruct, rid_values, comment)
	local value = valuestruct.value

	if value == nil or value < 0 or (self.money.coin + value) < 0 then
		-- todo :打错误日志
        filelog.sys_error("------------open_jackpot_coin error",self.rid, self.ip, self.money.coin, valuestruct, comment)
		return false
	end
	local bd = self.money.coin
	local setargs = self:change_money({coin = value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
		local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.JACKPOT, self.rid, self.info.level, value, 0, 0,bd,self.money.coin,self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
		skynet.send(".coinlog", "lua", "write", logstr)
		return setargs.coin
	else
		return false
	end
end

--扣奖池抽水，valuestruct={beginvalue, value, endvalue}, 这里的value是正值
function Agent:jackpot_sub_diamond(valuestruct, rid_values, comment, nodename)
	-- 先校验 beginvalue - value = endvalue <== 注意这里要 减 value
	local value = valuestruct.value

	if value == nil or value <0 or (self.money.diamond - value) < 0  then
		-- todo :打错误日志
        filelog.sys_error("------------jackpot_sub_diamond error",self.rid, self.ip, self.money.diamond, rid_values, comment)
		return false
	end
	local bd = self.money.diamond
	local setargs = self:change_money({diamond = -value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
        local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.SUBJACKPOT, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.diamond,self.money.lock_diamond, comment.table_uuid, comment.start_time, comment.table_tradeid)
		skynet.send(".diamondlog", "lua", "write_player_jackpot_redis", logstr, self.rid)
		return setargs.diamond
	else
		return false
	end
end

--扣奖池抽水，valuestruct={beginvalue, value, endvalue}, 这里的value是正值
function Agent:jackpot_sub_coin(valuestruct, rid_values, comment, nodename)
	-- 先校验 beginvalue - value = endvalue <== 注意这里要 减 value
	local value = valuestruct.value

	if value == nil or value <0 or (self.money.coin - value) < 0  then
		-- todo :打错误日志
        filelog.sys_error("------------jackpot_sub_coin error",self.rid, self.ip, self.money.coin, rid_values, comment)
		return false
	end
	local bd = self.money.coin
	local setargs = self:change_money({coin = -value})
	filelog.print("line648", setargs)
	if setargs then
		-- 内容：原因，rid，改变量，参数给的bv，后来的ev， 开始钻石值，后来钻石值，银行钻石值， table_uuid， table_tradeid
		local logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", ECoinChange.SUBJACKPOT, self.rid, self.info.level, 0-value, valuestruct.beginvalue, valuestruct.endvalue,bd,self.money.coin, self.money.lock_coin, comment.table_uuid, comment.start_time, comment.table_tradeid)
        skynet.send(".coinlog", "lua", "write", logstr)
		return setargs.coin
	else
		return false
	end
end

--保存游戏数据,玩什么多少局某游戏
function Agent:save_gamedata(gamename, value, is_matchtable, valuestruct)
    --保存局数,暂不保存
    if false then
        local key = (is_matchtable and "matchtable") or "selftable"
        self.gamedata_list[key] = self.gamedata_list[key] or {}
        self.gamedata_list[key][gamename] = (self.gamedata_list[key][gamename] or 0) + 1
        self.info.gamedata = json.encode(self.gamedata_list)
        --更新数据库
        skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rs_otherinfo", nil, {gamedata = self.info.gamedata})
    end
    
    --无输赢，直接返回
    if not value or value == 0 then
        return
    end

    --记录今日流水
	local today_water = self.money.today_water or 0
	local key = self.rid .. ":lastupwatertime"
	local real_water = math.abs(valuestruct.real_water or value or 0)
	local result, result_data = redisdao.query_data(".gmmqredis", "get", key)
	local record_time = tonumber(result_data or 0) or 0
	local curtime = timetool.get_time()
	local curday = os.date("%d", curtime)
	if record_time ~= 0 then
		local lastday = os.date("%d", record_time)
		if lastday ~= curday then
			today_water = 0
		end
	end
	self.lastupwatertime = curtime
	self.money.today_water = today_water + real_water
	redisdao.query_data(".gmmqredis", "set", key, tostring(curtime))
	
	----------------------------------------------------------------------
	---一局游戏结束，更新阶段打码值，累计打码值，基础打码值
	--
	
	local limit_chipvalue = self.money.limit_chipvalue or 0
	local phase_chipvalue = self.money.phase_chipvalue or 0
	local total_chipvalue = self.money.total_chipvalue or 0
	local base_chipvalue = self.money.base_chipvalue or 0
	
	---------------------------
	--[[
	定义
	
	本局打码值（新增打码值）：为正值，为两者最小，min(下注值，流水值)。做庄时，本局赢不计算打码值

	累积打码值：新玩家为0，每局结束后计算。旧值+本局打码值为新值。约等于累计流水值
	
	基础打码值：新玩家为0，每局结束后计算。旧值-本局打码值为新值。
	如果玩家提现或赠送，基础打码值保持不变。如果玩家充值、收款，基础打码值=旧值+充值金额
	活动奖励和充值奖励，基础打码值=旧值+3*奖励金额
	
	阶段打码值：上次提现后到现在为止这一阶段内的累计打码值
	
	打码限额值：上次提现后到现在为止的累计充值金额

	如果代理后台转账到游戏，基础打码值，打码限额值不受影响
	--]]
	
	--
	--计算新增打码值
	--本局输：新增打码值=|流水值|，本局赢：新增打码值=min(下注值,流水值）
	--本局输时，|流水值|=下注值，则本局新增打码值=|min(下注值,流水值)|
	--total_bet下注值 value流水值 inc_chipvalue新增打码值
	--
	local total_bet = valuestruct.total_bet or 0
	local inc_chipvalue = math.abs(math.min(total_bet, value))
	if total_bet == 0 then 
		inc_chipvalue = math.abs(value) 
	end
	
	--计算累计打码值
	--累计打码值=旧累计打码值+新增打码值
	total_chipvalue = total_chipvalue + inc_chipvalue
	
	--计算基础打码值
	--基础打码值=旧基础打码值-新增打码值
	base_chipvalue = base_chipvalue - inc_chipvalue
	if base_chipvalue < 0 then base_chipvalue = 0 end
	
	--计算阶段打码值,限额打码值大于0才计算
	if limit_chipvalue > 0 then
		phase_chipvalue = phase_chipvalue + inc_chipvalue
	end
	
	----------------------------------------------------------------
	
	--更新数据
	local updatelist = {}
	updatelist.phase_chipvalue = phase_chipvalue
	updatelist.total_chipvalue = total_chipvalue
	updatelist.base_chipvalue = base_chipvalue
	updatelist.today_water = self.money.today_water
	self.money.phase_chipvalue = phase_chipvalue
	self.money.total_chipvalue = total_chipvalue
	self.money.base_chipvalue = base_chipvalue
	
	--记录总输赢
    if value > 0 then
        self.money.total_win = self.money.total_win + value
		updatelist.total_win = self.money.total_win
        skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rs_money", 0, updatelist)
    else
        self.money.total_lose = self.money.total_lose - value
		updatelist.total_lose = self.money.total_lose
        skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rs_money", 0, updatelist)
    end

    --暂不记录输赢
    if true then
        return
    end

    --保存输赢记录
    local keyid = nil
    local sqlstr = "select * from rm_gamerecord where rid = " .. (self.rid) .. " and gamename = '" .. gamename .. "'"
    local status,resul_data = skynet.pcall(cluster.call, "datanode", ".router", "mysqlquery", 1, sqlstr)
    if status and resul_data and resul_data[1] ~= 0 and resul_data[2] then
        local key = nil
        local fields = { }
        if value < 0 then
            key = "total_lose"
            fields.total_lose = -value
        else
            key = "total_win"
            fields.total_win = value
        end
        local gamerecord = resul_data[2][1]
        if gamerecord then
            keyid = tonumber(gamerecord.keyid)
            fields[key] = fields[key] + gamerecord[key] 
            skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rm_gamerecord", keyid, fields)
        else
            fields.gamename = gamename
            keyid = skynet.call(".nodeuuid", "lua", "getuuid")
            skynet.pcall(cluster.send, "datanode", ".router", "insert_rk", self.rid, "rm_gamerecord", keyid, fields)
        end
    end
end

function Agent:check_role_inmatch(rid)
	if self.match_gamenode == "" and self.match_tabletype == nil then
		return false
	elseif self.match_gamenode and self.match_tabletype then
		return true
	end
	return true
end

--检测群发邮件，未收到群发邮件则给自己发送
function Agent:check_massmail()
    local massmail_keys = {}
    local self_massmail = self.info.massmail_list or {}
    local massmail_list = skynet.call(".router", "lua", "getmassmail") or {}

    for _,v in ipairs(self_massmail) do
        local keyid = tonumber(v) or 0
        massmail_keys[keyid] = true
    end
    
    local new_massmail_list = {}
    local cur_time = os.time()
    for _,item in ipairs(massmail_list) do
        if item.valid_time > cur_time then
            local keyid = tonumber(item.keyid)
            if keyid and not massmail_keys[keyid] then
                skynet.send(".mailbox", "lua", "newmail", self.rid, item.fields)
            end
            table.insert(new_massmail_list,keyid)
        end
    end
    self.info.massmail_list = new_massmail_list

    --更新数据库
    skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rs_otherinfo", nil, {massmail_list = json.encode(self.info.massmail_list)})
end

--发送群发邮件
function Agent:send_massmail(keyid, fields)
    local max_massmail_num = 20
    local self_massmail = self.info.massmail_list or {}

    while (#self_massmail >= max_massmail_num) do
        table.remove(self_massmail,1)
    end
    table.insert(self_massmail,keyid)
    self.info.massmail_list = self_massmail

    --发送邮件
    skynet.send(".mailbox", "lua", "newmail", self.rid, fields)

    --更新数据库
    skynet.pcall(cluster.send, "datanode", ".router", "update_rk", self.rid, "rs_otherinfo", nil, {massmail_list = json.encode(self.info.massmail_list)})
end

--
--更新提现状态
--
function Agent:update_withdraw_state(keyid, newstate, remark)
    remark = remark or ""
    newstate = tonumber(newstate) or 0
    keyid = tostring(keyid) or ""   --唯一id,数据库为int类型，但取出时已转换为string

    local rid = self.rid
    local result = cluster.call("datanode", ".router", "select_rk", rid, "rm_withdraw")
	if not result[2] or result[2][keyid] == nil then
        return false
    end

    --更新状态
    local update_list = {}
    local recorditem = result[2][keyid]
    local curr_state = tonumber(recorditem.current_state)
    if newstate > curr_state and curr_state <= EWithdrawState.EWITHDRAW_STATE_COMPLETE 
    and newstate <= EWithdrawState.EWITHDRAW_STATE_BACKED then
        recorditem.current_state = newstate
        update_list.current_state = recorditem.current_state
        if newstate == EWithdrawState.EWITHDRAW_STATE_IN_HANDLE then    --处理中
            recorditem.handle_time = timetool.get_time()
            update_list.handle_time = recorditem.handle_time
        elseif newstate == EWithdrawState.EWITHDRAW_STATE_COMPLETE      --已完成
        or newstate == EWithdrawState.EWITHDRAW_STATE_REFUSED           --拒绝
        or newstate == EWithdrawState.EWITHDRAW_STATE_BACKED            --退回
        or newstate == EWithdrawState.EWITHDRAW_STATE_FAILED then       --失败
            recorditem.complete_time = timetool.get_time()
            update_list.complete_time = recorditem.complete_time
            update_list.remark = remark
        end

        --提现失败，回滚
        if recorditem.current_state == EWithdrawState.EWITHDRAW_STATE_FAILED 
        or recorditem.current_state == EWithdrawState.EWITHDRAW_STATE_BACKED then
            --直接加钻石
            local beginvalue = self.money.diamond or 0
            local service_fee = tonumber(recorditem.service_fee) or 0
            local withdraw_value = tonumber(recorditem.withdraw_value) or 0
            local addvalue = service_fee + withdraw_value
            local setargs = true --self:change_money({diamond = addvalue}, { })
            if setargs then
                --记录日志
                local logstr = nil
                local comment = { }
                local now = timetool.get_time()
                comment.tradeid = keyid
                comment.table_uuid = "gatenode" .. "_" .. 0 .. "_" .. 0 .. "_" .. now
                logstr = string.format("%d,%d,%d,%d,%d,%d,%d,%d,%d,%s,%s,%s", EDiamondChange.WITHDRAW_RETURN, rid, self.info.level, addvalue, 0, 0,
                beginvalue, self.money.diamond, self.money.lock_diamond, comment.table_uuid, now, comment.tradeid)
                skynet.send(".diamondlog", "lua", "write", logstr)
            end

            --提现失败，邮件通知
            local content = "在 " .. os.date("%m-%d %H:%M:%S",tonumber(recorditem.create_time)) .. " 提现金币被退回。 退回原因：" .. (remark or "")
            local fields = {subject = "金币提现退回", mail_type = EMailType.EMAIL_TYPE_TXBACK, content = content, attach_json = json.encode({diamond = addvalue}) }
            skynet.send(".mailbox", "lua", "newmail", self.rid, fields)
			
         elseif recorditem.current_state == EWithdrawState.EWITHDRAW_STATE_COMPLETE then
            --提现成功，更新提现总金额
            self.money.total_withdraw = self.money.total_withdraw + tonumber(recorditem.withdraw_value)
            self.money.total_servicefee = self.money.total_servicefee + tonumber(recorditem.service_fee)
            local updatelist = {total_withdraw = self.money.total_withdraw, total_servicefee = self.money.total_servicefee}
            skynet.pcall(cluster.send, "datanode", ".router", "update_rk", rid, "rs_money", 0, updatelist)
         end

        --更新提现记录
	    skynet.pcall(cluster.call, "datanode", ".router", "update_rk", rid, "rm_withdraw", keyid, update_list)
        return true
    elseif newstate == EWithdrawState.EWITHDRAW_STATE_WAIT_HANDLE 
    and curr_state == EWithdrawState.EWITHDRAW_STATE_IN_HANDLE then --处理状态变成待处理状态
        update_list.handle_time = 0
        update_list.current_state = newstate
	    skynet.pcall(cluster.call, "datanode", ".router", "update_rk", rid, "rm_withdraw", keyid, update_list)
        return true
    end

    return false  
end

--
--刷新金钱
--
function Agent:refresh_money(change_value)
	local rid = self.rid or 0
	local result = cluster.call("datanode", ".router", "select_rk", rid, "rs_money")
	if result and result[1] ~= 0 and result[2] and self.money then
		local moneydata = result[2][rid]
		local beginvalue = self.money.diamond
		if moneydata then
			self.money.diamond = tonumber(moneydata.diamond)
			self.money.limit_chipvalue = tonumber(moneydata.limit_chipvalue)
			self.money.base_chipvalue = tonumber(moneydata.base_chipvalue)
		end
		
		local value = self.money.diamond - beginvalue
		if value > 0 and self.gamenode ~= "" then
			pcall(cluster.send, self.gamenode, ".router", "rid_change_diamond", self.rid, beginvalue, value, self.money.diamond)
		end
		
		--通知客户端
		if value ~= 0 then
			local msgbody = {}
			msgbody.rid = self.rid
			msgbody.diamond = self.money.diamond
			msgbody.lock_diamond = self.money.lock_diamond
			msgbody.fangka = self.money.fangka
	        msgbody.coin = self.money.coin
	        msgbody.lock_coin = self.money.lock_coin
			self:notify_client("changemoneyN",  {rolemoney = msgbody})
		end
	end
	
end

--
--获取活动信息
--
function Agent:getactivityinfo(rid, msgbody)
	msgbody = msgbody or {}
	
	local response = { }

	--获得活动映射列表
	if not self.activity_index_list then
		local result, data = redisdao.query_data(".gmmqredis", "get", "activity_index_list")
		if result and data then
			self.activity_index_list = json.decode(data) or nil
		end
	end

	--获得活动奖励列表
	local activity_index_list = self.activity_index_list
	local activity_id = tostring(msgbody.activity_id or 0)
	if not activity_index_list or not activity_index_list[activity_id] then
		return {retcode = EErrCode.ERR_INVALID_REQUEST, retstr = "获取活动信息失败"}
	end

	local activity_prize_list = nil
	local activity_key = activity_index_list[activity_id]
	local result, data = redisdao.query_data(".gmmqredis", "get", activity_key)
	if result and data then
		activity_prize_list = data
	end
	response.activity_id = msgbody.activity_id
	response.activity_prize_list = activity_prize_list

	return response
end

skynet.start(function()
	Agent:start()
end)
